#include "common.h"
#include <openssl/err.h>

BIO *bio_err = 0;
static const char *pass;
static int password_cb(char *buf, int num, int rwflag, void *userdata);
static void sigpipe_handle(int x);

/* A simple error and exit routine*/
int err_exit(const char* string) {
	fprintf(stderr, "%s\n", string);
	exit(0);
}

/* Print SSL errors and exit*/
int berr_exit(const char* string) {
	BIO_printf(bio_err, "%s\n", string);
	ERR_print_errors(bio_err);
	exit(0);
}

/*The password code is not thread safe*/
static int password_cb(char *buf, int num, int rwflag, void *userdata) {
	if (num < strlen(pass) + 1)
		return (0);

	strcpy(buf, pass);
	return (strlen(pass));
}

static void sigpipe_handle(int x) {
}

SSL_CTX *initialize_ctx(const char* keyfile, const char* password) {
	SSL_METHOD *meth;
	SSL_CTX *ctx;

	if (!bio_err) {
		/* Global system initialization*/
		SSL_library_init();
		SSL_load_error_strings();

		/* An error write context */
		bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
	}

	/* Set up a SIGPIPE handler */
	signal(SIGPIPE, sigpipe_handle);

	/* Create our context*/
	meth = SSLv23_method();
	ctx = SSL_CTX_new(meth);

	/* Load our keys and certificates*/
	if (!(SSL_CTX_use_certificate_chain_file(ctx, keyfile)))
		berr_exit("Can't read certificate file");

	pass = password;
	SSL_CTX_set_default_passwd_cb(ctx, password_cb);
	if (!(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)))
		berr_exit("Can't read key file");

	/* Load the CAs we trust*/
	if (!(SSL_CTX_load_verify_locations(ctx, CA_LIST, 0)))
		berr_exit("Can't read CA list");

#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
	SSL_CTX_set_verify_depth(ctx, 1);
#endif

	return ctx;
}

void destroy_ctx(SSL_CTX *ctx) {
	SSL_CTX_free(ctx);
}

int SSL_read_until_full(SSL* ssl, void* buf, int buf_size) {
	int r;
	int len = 0;
	unsigned char* my_buf = (unsigned char*) buf;

	do {
		r = SSL_read(ssl, my_buf + len, buf_size - len);

		if (SSL_get_error(ssl, r) != SSL_ERROR_NONE) {
			berr_exit("SSL read problem");
		}

		len += r;
	} while (len < buf_size);

	return (len);
}

bool generate_rsa_keys(const char* rsa_pubkey_fname, const char* rsa_privkey_fname) {
	RSA* rsa = RSA_generate_key(PUB_MOD_BITS, PUB_EXP, NULL, 0);
	if (RSA_check_key(rsa) <= 0) {
		RSA_free(rsa);
		return false;
	}
	RSA_blinding_on(rsa, NULL);

	BIO* rsa_pub_bio = BIO_new_file(rsa_pubkey_fname, "w");
	PEM_write_bio_RSA_PUBKEY(rsa_pub_bio, rsa);

	BIO* rsa_priv_bio = BIO_new_file(rsa_privkey_fname, "w");
	PEM_write_bio_RSAPrivateKey(rsa_priv_bio, rsa, NULL, NULL, 0, NULL, NULL);

	BIO_free(rsa_pub_bio);
	BIO_free(rsa_priv_bio);
	RSA_free(rsa);
	return true;
}

unsigned char* sha1_digest(unsigned char* buf, unsigned int buf_len, unsigned int* hash_bytes) {
	const EVP_MD* m = EVP_sha1();
	EVP_MD_CTX ctx;
	unsigned char* hash = new unsigned char[EVP_MAX_MD_SIZE];

	EVP_DigestInit(&ctx, m);
	EVP_DigestUpdate(&ctx, buf, buf_len);
	EVP_DigestFinal(&ctx, hash, hash_bytes);
	return hash;
}

unsigned char* prep_for_digest(const struct ShippingInfo& ship_info) {
	int attn_first_name_len = strlen(ship_info.attn_first_name);
	int attn_mid_name_len = strlen(ship_info.attn_mid_name);
	int attn_last_name_len = strlen(ship_info.attn_last_name);
	int shipping_addr_len = strlen(ship_info.shipping_addr);
	int contact_num_len = strlen(ship_info.contact_num);
	int handling_req_len = strlen(ship_info.handling_req);
	int carrier_code_len = strlen(ship_info.carrier_code);
	int pub_key_len = strlen(ship_info.pub_key);
	int order_token_len = strlen(ship_info.order_token);

	const int bytes = attn_first_name_len + attn_mid_name_len
			+ attn_last_name_len + shipping_addr_len + contact_num_len
			+ handling_req_len + carrier_code_len + pub_key_len
			+ order_token_len + 1;
	unsigned char* buf = new unsigned char[bytes];
	memset(buf, 0, bytes);

	int i = 0;
	strncpy((char*) (buf + i), ship_info.attn_first_name, attn_first_name_len);
	i += attn_first_name_len;
	strncpy((char*) (buf + i), ship_info.attn_mid_name, attn_mid_name_len);
	i += attn_mid_name_len;
	strncpy((char*) (buf + i), ship_info.attn_last_name, attn_last_name_len);
	i += attn_last_name_len;
	strncpy((char*) (buf + i), ship_info.shipping_addr, shipping_addr_len);
	i += shipping_addr_len;
	strncpy((char*) (buf + i), ship_info.contact_num, contact_num_len);
	i += contact_num_len;
	strncpy((char*) (buf + i), ship_info.handling_req, handling_req_len);
	i += handling_req_len;
	strncpy((char*) (buf + i), ship_info.carrier_code, carrier_code_len);
	i += carrier_code_len;
	strncpy((char*) (buf + i), ship_info.pub_key, pub_key_len);
	i += pub_key_len;
	strncpy((char*) (buf + i), ship_info.order_token, order_token_len);
	i += order_token_len;
	return buf;
}

unsigned char* prep_for_digest(const struct PaymentInfo& pay_info) {
	int ch_first_name_len = strlen(pay_info.ch_first_name);
	int ch_mid_name_len = strlen(pay_info.ch_mid_name);
	int ch_last_name_len = strlen(pay_info.ch_last_name);
	int card_num_len = strlen(pay_info.card_num);
	int exp_date_len = strlen(pay_info.exp_date);
	int payment_amount_len = strlen(pay_info.payment_amount);
	int payment_date_len = strlen(pay_info.payment_date);
	int order_token_len = strlen(pay_info.order_token);

	const int bytes = ch_first_name_len + ch_mid_name_len + ch_last_name_len
			+ card_num_len + exp_date_len + payment_amount_len
			+ payment_date_len + order_token_len + 1;
	unsigned char* buf = new unsigned char[bytes];
	memset(buf, 0, bytes);

	int i = 0;
	strncpy((char*) (buf + i), pay_info.ch_first_name, ch_first_name_len);
	i += ch_first_name_len;
	strncpy((char*) (buf + i), pay_info.ch_mid_name, ch_mid_name_len);
	i += ch_mid_name_len;
	strncpy((char*) (buf + i), pay_info.ch_last_name, ch_last_name_len);
	i += ch_last_name_len;
	strncpy((char*) (buf + i), pay_info.card_num, card_num_len);
	i += card_num_len;
	strncpy((char*) (buf + i), pay_info.exp_date, exp_date_len);
	i += exp_date_len;
	strncpy((char*) (buf + i), pay_info.payment_amount, payment_amount_len);
	i += payment_amount_len;
	strncpy((char*) (buf + i), pay_info.payment_date, payment_date_len);
	i += payment_date_len;
	strncpy((char*) (buf + i), pay_info.order_token, order_token_len);
	i += order_token_len;
	return buf;
}

void timespec_diff(struct timespec* start, struct timespec* end, struct timespec* diff)
{
  long int nsec_diff = end->tv_nsec - start->tv_nsec;

  if (nsec_diff < 0)
  {
    start->tv_nsec = 1000000000l + nsec_diff;
    diff->tv_sec= end->tv_sec - start->tv_sec - 1;
  }
  else
  {
    diff->tv_nsec = nsec_diff;
    diff->tv_sec= end->tv_sec - start->tv_sec;
  }

}
